/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.lucene.queries.function;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.FilterScorer;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Matches;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreMode;
import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Weight;

import com.xyq.fs.constants.FieldConstant;
import com.xyq.fs.search.MySearch;

/**
 * A query that wraps another query, and uses a DoubleValuesSource to replace or
 * modify the wrapped query's score
 *
 * If the DoubleValuesSource doesn't return a value for a particular document,
 * then that document will be given a score of 0.
 */
public final class FunctionScoreQuery extends Query {

	private final Query in;
	private final DoubleValuesSource source;
	static Map<Integer, Document> cache = new HashMap<>();
	static Map<Integer, String> cache2 = new HashMap<>();

	/**
	 * Create a new FunctionScoreQuery
	 * 
	 * @param in
	 *            the query to wrap
	 * @param source
	 *            a source of scores
	 */
	public FunctionScoreQuery(Query in, DoubleValuesSource source) {
		this.in = in;
		this.source = source;
	}

	/**
	 * @return the wrapped Query
	 */
	public Query getWrappedQuery() {
		return in;
	}

	/**
	 * @return the underlying value source
	 */
	public DoubleValuesSource getSource() {
		return source;
	}

	/**
	 * Returns a FunctionScoreQuery where the scores of a wrapped query are
	 * multiplied by the value of a DoubleValuesSource.
	 *
	 * If the source has no value for a particular document, the score for that
	 * document is preserved as-is.
	 *
	 * @param in
	 *            the query to boost
	 * @param boost
	 *            a {@link DoubleValuesSource} containing the boost values
	 */
	public static FunctionScoreQuery boostByValue(Query in, DoubleValuesSource boost) {
		return new FunctionScoreQuery(in, new MultiplicativeBoostValuesSource(boost));
	}

	/**
	 * Returns a FunctionScoreQuery where the scores of a wrapped query are
	 * multiplied by a boost factor if the document being scored also matches a
	 * separate boosting query.
	 *
	 * Documents that do not match the boosting query have their scores
	 * preserved.
	 *
	 * This may be used to 'demote' documents that match the boosting query, by
	 * passing in a boostValue between 0 and 1.
	 *
	 * @param in
	 *            the query to boost
	 * @param boostMatch
	 *            the boosting query
	 * @param boostValue
	 *            the amount to boost documents which match the boosting query
	 */

	public static FunctionScoreQuery boostByQuery(Query in, Query boostMatch, float boostValue) {
		// return new FunctionScoreQuery(in, new
		// MultiplicativeBoostValuesSource(
		// new QueryBoostValuesSource(DoubleValuesSource.fromQuery(boostMatch),
		// boostValue)));
		return new FunctionScoreQuery(in, DoubleValuesSource.fromQuery(in));
	}

	public static FunctionScoreQuery boostByQuery(Query in) {

		return new FunctionScoreQuery(in, DoubleValuesSource.fromQuery(in));
	}

	@Override
	public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException {
		Weight inner = in.createWeight(searcher,
				scoreMode.needsScores() && source.needsScores() ? scoreMode : ScoreMode.COMPLETE_NO_SCORES, 1f);
		if (scoreMode.needsScores() == false)
			return inner;
		return new FunctionScoreWeight(this, inner, source.rewrite(searcher), boost);
	}

	@Override
	public Query rewrite(IndexReader reader) throws IOException {
		Query rewritten = in.rewrite(reader);
		if (rewritten == in)
			return this;
		return new FunctionScoreQuery(rewritten, source);
	}

	@Override
	public String toString(String field) {
		return "FunctionScoreQuery(" + in.toString(field) + ", scored by " + source.toString() + ")";
	}

	@Override
	public boolean equals(Object o) {
		if (this == o)
			return true;
		if (o == null || getClass() != o.getClass())
			return false;
		FunctionScoreQuery that = (FunctionScoreQuery) o;
		return Objects.equals(in, that.in) && Objects.equals(source, that.source);
	}

	@Override
	public int hashCode() {
		return Objects.hash(in, source);
	}

	private static class FunctionScoreWeight extends Weight {

		final Weight inner;
		final DoubleValuesSource valueSource;
		final float boost;

		FunctionScoreWeight(Query query, Weight inner, DoubleValuesSource valueSource, float boost) {
			super(query);
			this.inner = inner;
			this.valueSource = valueSource;
			this.boost = 0;
		}

		@SuppressWarnings("deprecation")
		@Override
		public void extractTerms(Set<Term> terms) {
			this.inner.extractTerms(terms);
		}

		@Override
		public Matches matches(LeafReaderContext context, int doc) throws IOException {
			return inner.matches(context, doc);
		}

		@Override
		public Explanation explain(LeafReaderContext context, int doc) throws IOException {
			Explanation scoreExplanation = inner.explain(context, doc);
			if (scoreExplanation.isMatch() == false) {
				return scoreExplanation;
			}

			Scorer scorer = inner.scorer(context);
			DoubleValues values = valueSource.getValues(context, DoubleValuesSource.fromScorer(scorer));
			int advanced = scorer.iterator().advance(doc);
			assert advanced == doc;

			double value;
			Explanation expl;
			if (values.advanceExact(doc)) {
				value = values.doubleValue();
				expl = valueSource.explain(context, doc, scoreExplanation);
				if (value < 0) {
					value = 0;
					expl = Explanation.match(0, "truncated score, max of:", Explanation.match(0f, "minimum score"),
							expl);
				} else if (Double.isNaN(value)) {
					value = 0;
					expl = Explanation.match(0,
							"score, computed as (score == NaN ? 0 : score) since NaN is an illegal score from:", expl);
				}
			} else {
				value = 0;
				expl = valueSource.explain(context, doc, scoreExplanation);
			}

			if (expl.isMatch() == false) {
				expl = Explanation.match(0f, "weight(" + getQuery().toString()
						+ ") using default score of 0 because the function produced no value:", expl);
			} else if (boost != 1f) {
				expl = Explanation.match((float) (value * boost), "weight(" + getQuery().toString() + "), product of:",
						Explanation.match(boost, "boost"), expl);
			} else {
				expl = Explanation.match(expl.getValue(), "weight(" + getQuery().toString() + "), result of:", expl);
			}
			return expl;
		}

		/**
		 * 自定义评分
		 */

		@Override
		public Scorer scorer(LeafReaderContext context) throws IOException {

			// System.out.println("吊我");
			Scorer in = inner.scorer(context);
			LeafReader LR = context.reader();
			if (in == null)
				return null;
			return new FilterScorer(in) {
				@Override
				public float score() throws IOException {

					int valSrcScore = 0;
					int id = in.docID();
					String fname;
					fname = cache2.get(id);
					if (fname == null) {
						Document docc = LR.document(id);
						fname = docc.get(FieldConstant.FILE_NAME);
						cache2.put(id, fname);
					}
					if (MySearch.PUBLICKEY != null) {
						for (String s : MySearch.PUBLICKEY) {
							valSrcScore += searchCount(fname, s);
						}
					}
					// long lo =
					// Long.parseLong(docc.get(FieldConstant.FILE_UPDATE_TIME));
					// valSrcScore += lo;
					return valSrcScore;
				}

				private int searchCount(String longStr, String shortStr) {

					int count = 0;

					while (longStr.indexOf(shortStr) != -1) {
						count++;
						longStr = longStr.substring(longStr.indexOf(shortStr) + shortStr.length());
					}
					return count;
				}

				@Override
				public float getMaxScore(int upTo) throws IOException {
					return Float.POSITIVE_INFINITY;
				}
			};
		}

		@Override
		public boolean isCacheable(LeafReaderContext ctx) {
			return inner.isCacheable(ctx) && valueSource.isCacheable(ctx);
		}

	}

	private static class MultiplicativeBoostValuesSource extends DoubleValuesSource {

		private final DoubleValuesSource boost;

		private MultiplicativeBoostValuesSource(DoubleValuesSource boost) {
			this.boost = boost;
		}

		@Override
		public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
			DoubleValues in = DoubleValues.withDefault(boost.getValues(ctx, scores), 1);
			return new DoubleValues() {
				@Override
				public double doubleValue() throws IOException {
					return scores.doubleValue() * in.doubleValue();
				}

				@Override
				public boolean advanceExact(int doc) throws IOException {
					return in.advanceExact(doc);
				}
			};
		}

		@Override
		public boolean needsScores() {
			return true;
		}

		@Override
		public DoubleValuesSource rewrite(IndexSearcher reader) throws IOException {
			return new MultiplicativeBoostValuesSource(boost.rewrite(reader));
		}

		@Override
		public boolean equals(Object o) {
			if (this == o)
				return true;
			if (o == null || getClass() != o.getClass())
				return false;
			MultiplicativeBoostValuesSource that = (MultiplicativeBoostValuesSource) o;
			return Objects.equals(boost, that.boost);
		}

		@Override
		public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException {
			if (scoreExplanation.isMatch() == false) {
				return scoreExplanation;
			}
			Explanation boostExpl = boost.explain(ctx, docId, scoreExplanation);
			if (boostExpl.isMatch() == false) {
				return scoreExplanation;
			}
			return Explanation.match(scoreExplanation.getValue().doubleValue() * boostExpl.getValue().doubleValue(),
					"product of:", scoreExplanation, boostExpl);
		}

		@Override
		public int hashCode() {
			return Objects.hash(boost);
		}

		@Override
		public String toString() {
			return "boost(" + boost.toString() + ")";
		}

		@Override
		public boolean isCacheable(LeafReaderContext ctx) {
			return boost.isCacheable(ctx);
		}
	}

	public static void main(String[] args) {

		long lg = 1559455470669l;
		float ft = 1000000000000l;
		System.out.println(ft + lg);
	}
}
